﻿@page "/game_tetris"
@implements IDisposable
@using System.Threading

<h2>Tetris</h2>
<p>Score: @Score</p>
<button @onclick="Restart">重新开始</button>
<button @onclick="TogglePause">@(Paused ? "继续" : "暂停")</button>

<div class="game-container">
    <div class="board-container">
        <div class="board"
             tabindex="0"
             @onkeydown="HandleKey"
             @ref="boardRef">
            @for (var r = 0; r < GRID_H; r++)
            {
                <div class="row">
                    @for (var c = 0; c < GRID_W; c++)
                    {
                        var v = Grid[r, c];
                        <div class="cell"
                             style="background-color:@(v==0 ? "#1e1e1e" : Colors[v-1])"></div>
                    }
                </div>
            }

            <!-- 当前方块层 -->
            @if (CurrentPiece != null)
            {
                <div class="overlay">
                    @for (var r = 0; r < CurrentPiece.Length; r++)
                    {
                        @for (var c = 0; c < CurrentPiece[r].Length; c++)
                        {
                            if (CurrentPiece[r][c] == 1)
                            {
                                var displayX = CurrentX + c;
                                var displayY = CurrentY + r;
                                if (displayY >= 0) // 只显示在棋盘内的部分
                                {
                                    <div class="cell active"
                                         style="grid-column-start: @(displayX+1); grid-row-start: @(displayY+1);
                                                background-color:@Colors[CurrentIndex]"></div>
                                }
                            }
                        }
                    }
                </div>
            }

            @if (GameOver)
            {
                <div class="gameover">
                    <span>游戏结束</span>
                    <span>Score: @Score</span>
                </div>
            }
            @if (Paused && !GameOver)
            {
                <div class="paused">暂停</div>
            }
        </div>
    </div>

    <div class="side-panel">
        <h3>下一个方块</h3>
        <div class="next-piece">
            @if (NextPiece != null)
            {
                @for (var r = 0; r < NextPiece.Length; r++)
                {
                    @for (var c = 0; c < NextPiece[r].Length; c++)
                    {
                        if (NextPiece[r][c] == 1)
                        {
                            <div class="mini-cell" style="background-color:@Colors[NextIndex]"></div>
                        }
                        else
                        {
                            <div class="mini-cell" style="background-color:#1e1e1e"></div>
                        }
                    }
                    <br />
                }
            }
        </div>
        <div class="instructions">
            <h3>操作</h3>
            <p>← → : 左右移动</p>
            <p>↑ : 旋转</p>
            <p>↓ : 加速下降</p>
            <p>Space : 暂停/继续</p>
        </div>
    </div>
</div>

@code {
    /* ====== 常量 ====== */
    const int GRID_W = 10, GRID_H = 20;
    static readonly int[][][] Shapes =
    {
        new[]{new[]{1,1,1,1}},                    // I
        new[]{new[]{1,0,0},new[]{1,1,1}},         // J
        new[]{new[]{0,0,1},new[]{1,1,1}},         // L
        new[]{new[]{1,1},new[]{1,1}},              // O
        new[]{new[]{0,1,1},new[]{1,1,0}},          // S
        new[]{new[]{0,1,0},new[]{1,1,1}},          // T
        new[]{new[]{1,1,0},new[]{0,1,1}}           // Z
    };
    static readonly string[] Colors = { "#0ff", "blue", "orange", "yellow", "lime", "purple", "red" };

    /* ====== 状态 ====== */
    int[,] Grid = new int[GRID_H, GRID_W];
    int[][] CurrentPiece = null!;
    int CurrentX, CurrentY, CurrentIndex;
    int[][] NextPiece = null!;
    int NextIndex;
    int Score, Speed = 500;
    bool GameOver, Paused;

    Timer? timer;
    ElementReference boardRef;
    DateTime lastFall = DateTime.Now;

    /* ====== 生命周期 ====== */
    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            await boardRef.FocusAsync();          // 让容器可收键盘
            timer = new Timer(_ => InvokeAsync(Update), null, 0, 50);
            NewPiece();
        }
    }

    /* ====== 游戏循环 ====== */
    void Update()
    {
        if (GameOver || Paused) 
        { 
            StateHasChanged(); 
            return; 
        }
        
        if ((DateTime.Now - lastFall).TotalMilliseconds < Speed) 
        { 
            StateHasChanged(); 
            return; 
        }

        if (!Collision(CurrentPiece, CurrentX, CurrentY + 1))
        {
            CurrentY++;
            lastFall = DateTime.Now;
        }
        else
        {
            Lock();
            NewPiece();
            if (Collision(CurrentPiece, CurrentX, CurrentY))
            {
                GameOver = true;
            }
        }
        StateHasChanged();
    }

    /* ====== 逻辑函数 ====== */
    void NewPiece()
    {
        if (NextPiece == null)
        {
            NextIndex = Random.Shared.Next(Shapes.Length);
            NextPiece = Shapes[NextIndex];
        }
        CurrentIndex = NextIndex;
        CurrentPiece = NextPiece;
        CurrentX = GRID_W / 2 - CurrentPiece[0].Length / 2;
        CurrentY = 0;

        NextIndex = Random.Shared.Next(Shapes.Length);
        NextPiece = Shapes[NextIndex];
    }

    static int[][] Rotate(int[][] p) =>
        Enumerable.Range(0, p[0].Length)
                  .Select(c => Enumerable.Range(0, p.Length)
                                         .Select(r => p[p.Length - 1 - r][c]).ToArray())
                  .ToArray();

    bool Collision(int[][] piece, int ox, int oy)
    {
        for (var r = 0; r < piece.Length; r++)
            for (var c = 0; c < piece[r].Length; c++)
                if (piece[r][c] == 1)
                {
                    int x = ox + c, y = oy + r;
                    if (x < 0 || x >= GRID_W || y >= GRID_H || (y >= 0 && Grid[y, x] != 0))
                        return true;
                }
        return false;
    }

    void Lock()
    {
        for (var r = 0; r < CurrentPiece.Length; r++)
            for (var c = 0; c < CurrentPiece[r].Length; c++)
                if (CurrentPiece[r][c] == 1)
                    Grid[CurrentY + r, CurrentX + c] = CurrentIndex + 1;

        int cleared = 0;
        for (var y = GRID_H - 1; y >= 0; y--)
        {
            bool full = true;
            for (var x = 0; x < GRID_W; x++) 
                if (Grid[y, x] == 0) 
                { 
                    full = false; 
                    break; 
                }
            if (full)
            {
                cleared++;
                for (var yy = y; yy > 0; yy--)
                    for (var x = 0; x < GRID_W; x++)
                        Grid[yy, x] = Grid[yy - 1, x];
                for (var x = 0; x < GRID_W; x++) 
                    Grid[0, x] = 0;
                y++; // 重新检查当前行
            }
        }
        Score += cleared switch { 1 => 100, 2 => 300, 3 => 600, 4 => 1000, _ => 0 };
    }

    /* ====== 键盘 ====== */
    async void HandleKey(KeyboardEventArgs e)
    {
        if (GameOver) 
            return;
            
        if (e.Code == "Space") 
        { 
            TogglePause(); 
            return; 
        }
        
        if (Paused) 
            return;

        switch (e.Code)
        {
            case "ArrowLeft":  
                if (!Collision(CurrentPiece, CurrentX - 1, CurrentY)) 
                    CurrentX--; 
                break;
            case "ArrowRight": 
                if (!Collision(CurrentPiece, CurrentX + 1, CurrentY)) 
                    CurrentX++; 
                break;
            case "ArrowDown":  
                if (!Collision(CurrentPiece, CurrentX, CurrentY + 1)) 
                    CurrentY++; 
                break;
            case "ArrowUp":
                var rot = Rotate(CurrentPiece);
                if (!Collision(rot, CurrentX, CurrentY)) 
                    CurrentPiece = rot;
                break;
        }
        StateHasChanged();
        await boardRef.FocusAsync(); // 重新获取焦点以确保键盘持续响应
    }

    void TogglePause()
    {
        Paused = !Paused;
        StateHasChanged();
    }

    /* ====== 重新开始 ====== */
    async Task Restart()
    {
        Grid = new int[GRID_H, GRID_W];
        Score = 0; 
        Speed = 500; 
        GameOver = false; 
        Paused = false;
        NextPiece = null;
        NewPiece();
        lastFall = DateTime.Now;
        StateHasChanged();
        await boardRef.FocusAsync(); // 重新获取焦点
    }

    /* ====== 清理 ====== */
    public void Dispose() => timer?.Dispose();
}

<style>
    .game-container {
        display: flex;
        gap: 20px;
        margin-top: 20px;
    }
    
    .board-container {
        flex: 1;
    }
    
    .side-panel {
        width: 150px;
        display: flex;
        flex-direction: column;
        gap: 20px;
    }
    
    .board {
        position: relative;
        display: grid;
        grid-template-columns: repeat(@GRID_W, 30px);
        grid-template-rows: repeat(@GRID_H, 30px);
        border: 2px solid #fff;
        outline: none;
        background-color: #1e1e1e;
    }
    
    .row {
        display: contents;
    }
    
    .cell {
        width: 28px;
        height: 28px;
        border: 1px solid #333;
    }
    
    .overlay {
        position: absolute;
        top: 0;
        left: 0;
        display: grid;
        grid-template-columns: repeat(@GRID_W, 30px);
        grid-template-rows: repeat(@GRID_H, 30px);
        pointer-events: none;
    }
    
    .active {
        border: 1px solid white;
        box-sizing: border-box;
    }
    
    .gameover, .paused {
        position: absolute;
        top: 0;
        left: 0;
        right: 0;
        bottom: 0;
        display: flex;
        flex-direction: column;
        align-items: center;
        justify-content: center;
        background: rgba(0,0,0,0.7);
        color: white;
        font-size: 2em;
        z-index: 10;
    }
    
    .next-piece {
        background-color: #1e1e1e;
        padding: 10px;
        border-radius: 5px;
        display: inline-block;
    }
    
    .mini-cell {
        width: 20px;
        height: 20px;
        display: inline-block;
        border: 1px solid #333;
    }
    
    .instructions {
        background-color: #2a2a2a;
        padding: 15px;
        border-radius: 5px;
        color: #ddd;
    }
    
    .instructions h3 {
        margin-top: 0;
        color: white;
    }
    
    .instructions p {
        margin: 5px 0;
    }
    
    button {
        padding: 8px 16px;
        margin-right: 10px;
        background-color: #4CAF50;
        color: white;
        border: none;
        border-radius: 4px;
        cursor: pointer;
    }
    
    button:hover {
        background-color: #45a049;
    }
</style>